package com.study.springsecurity.security.config;

import com.study.springsecurity.constant.CommonConstant.PageUrl;
import com.study.springsecurity.constant.CommonConstant.Role;
import com.study.springsecurity.filter.VerificationCodeFilter;
import com.study.springsecurity.security.handler.*;
import com.study.springsecurity.security.voters.PermitAllVoter;
import com.study.springsecurity.service.ApiInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.vote.AbstractAccessDecisionManager;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleHierarchyVoter;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.web.cors.CorsConfigurationSource;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration {
    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PersistentTokenRepository persistentTokenRepository;

    @Autowired
    private SessionRegistry sessionRegistry;

    @Autowired
    private CorsConfigurationSource corsConfigurationSource;

    @Autowired
    private RoleHierarchy roleHierarchy;
    @Autowired
    private ApiInfoService apiInfoService;

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
                .antMatchers("/static/**"); // 允许静态资源不经过认证 TODO 未生效，待排查
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 异常情况处理
        http.exceptionHandling((exceptionHandling) ->
                exceptionHandling
                        .accessDeniedHandler(new MyAccessDeniedHandler()) // 设置没有权限访问后续处理
                        .authenticationEntryPoint(new NotLoginAuthenticationEntryPoint()) // 设置用户未登录时访问需要登录的接口处理逻辑
        );

        // 表单登录
        http.formLogin()
                // 登录时传递用户名和密码使用的参数名
                .usernameParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY)
                .passwordParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY)

                // 修改默认的登录页为login.html，他会自动去根路径static文件夹下寻找login.html
                .loginPage(PageUrl.LOGIN_PAGE)

                // 设置登录接口地址，这个接口不是真实存在的，还是用的security给我们提供的，之所以要有这个配置，是login.html当中form表单提交的地址我们设置的是这个
                .loginProcessingUrl(PageUrl.LOGIN_URL)

                .successHandler(new MyAuthenticationSuccessHandler()) // 登录成功之后的处理器

                .failureHandler(new MyAuthenticationFailureHandler()) // 登录失败之后的处理器

                .permitAll(); // permitAll中文意思是许可所有的：所有的都遵循上面的配置的意思

        // 接口权限认证
        http.authorizeRequests()
                .antMatchers("/testCodeConfigMatcher/test1").hasAnyRole(Role.PLAT_USER_ROLE)
                // 未被前面antMatchers匹配的任何请求都限制访问
                .anyRequest().denyAll()

                //.expressionHandler()
                //.anyRequest().fullyAuthenticated()

                // 配置请求对象的处理器
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        // 配置url元数据，将ExpressionBasedFilterInvocationSecurityMetadataSource内置到自定义的FilterInvocationSecurityMetadataSource中，用其兜底，实现对功能的扩展，而不影响通过代码配置antMatchers的功能
                        FilterInvocationSecurityMetadataSource securityMetadataSource = object.getSecurityMetadataSource();
                        object.setSecurityMetadataSource(new ApiFilterInvocationSecurityMetadataSource(securityMetadataSource,apiInfoService));

                        // 追加url权限的决策器，将WebExpressionVoter放在最后。
                        // 如果新建一个AccessDecisionManager，则无法使用WebExpressionVoter，在某些情况下无法解析spel表达式，例如： .anyRequest().access("authenticated and @securityAnnotationCheck.hasSecurityAnnotation(request,authentication)");
                        AbstractAccessDecisionManager accessDecisionManager = (AbstractAccessDecisionManager) object.getAccessDecisionManager();
                        List<AccessDecisionVoter<?>> decisionVoters = accessDecisionManager.getDecisionVoters();
                        decisionVoters.add(0,new PermitAllVoter()); // 登录决策、匿名用户、记住我 决策投票者
                        decisionVoters.add(1,new AuthenticatedVoter()); // 登录决策、匿名用户、记住我 决策投票者
                        decisionVoters.add(2,new RoleHierarchyVoter(roleHierarchy)); // 对RoleVoter的扩展，支持了角色层级关系

                        //object.setAccessDecisionManager(accessDecisionManager);
                        //object.setRejectPublicInvocations(true);
                        return object;
                    }
                });

        //.accessDecisionManager(accessDecisionManager());


        // 退出,这里的/login/loginOut的请求是和前端的接口约定，是security给我们提供的,退出成功后跳转登录页/login.html 或者 执行Handler
        http.logout().logoutUrl(PageUrl.LOGIN_OUT_URL)
                .logoutSuccessHandler(new MyLogoutSuccessHandler())
                //.logoutSuccessUrl(PageUrl.LOGIN_PAGE)
                .permitAll();


        http.rememberMe() // 设置记住我
                .tokenRepository(persistentTokenRepository) // 配置token存储库
                // 设置有效时长180秒，默认 2 周时间。
                .tokenValiditySeconds(180)
                .userDetailsService(userDetailsService);

        http.sessionManagement() // 回话管理器
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 指定session创建策略

                .maximumSessions(1) // 同一个用户的最大会话数（同一个账号允许同时登录多个客户端的数量）
                .maxSessionsPreventsLogin(false) // 达到回话限制时的处理，true：当前会话无法登录成功  false：之前的会话失效
                .sessionRegistry(sessionRegistry); // 指定session注册器

        // 关闭 csrf
        //http.csrf().disable();
        //http.csrf().csrfTokenRepository();
        http.csrf();


        // https://blog.csdn.net/fd2025/article/details/124491570
        http.headers().frameOptions().sameOrigin(); // 页面只能被本站页面嵌入到iframe或者frame中

        http.cors().configurationSource(corsConfigurationSource);

        // 在用户密码认证Filter之前新增验证码检查的Filter
        http.addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    public AccessDecisionManager accessDecisionManager() {
        // 通过HttpSecurity配置的antMatchers匹配规则会被WebExpressionVoter处理
        // 我希望在WebExpressionVoter之后增加一个Voter，通过数据库动态查询角色权限，处理WebExpressionVoter决策未通过的情况
        List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
        AbstractSecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
        //expressionHandler.setApplicationContext();
//        decisionVoters.add(new WebExpressionVoter()); // 。。。
//        decisionVoters.add(new PermitAllVoter()); // 所有人可访问
//        decisionVoters.add(new AnonymousVoter()); // 匿名可访问
//        decisionVoters.add(new AuthenticatedVoter()); // 登录可访问
//        decisionVoters.add(new RoleHierarchyVoter(roleHierarchy)); // 对RoleVoter的扩展，支持了角色层级关系
        return new AffirmativeBased(decisionVoters); // AffirmativeBased是只要任一 AccessDecisionVoter 返回肯定的结果，便授予访问权限
    }
}
   